package com.apes.framework.jpa.spec.model;

import com.apes.framework.jpa.spec.PredicateBuilder;
import com.apes.framework.jpa.spec.Specifications;
import com.apes.framework.util.ReflectUtil;
import com.apes.framework.util.Tools;
import lombok.Data;
import org.springframework.data.jpa.domain.Specification;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 功能：条件组
 *
 * @author xul
 * @create 2018-07-12 14:21
 */
@Data
public class Group {

    private String operator = "and";
    private List<Filter> filters = new ArrayList<>();
    private List<Group> groups = new ArrayList<>();

    public <T> Specification<T> build(final boolean distinct) {
        PredicateBuilder pb;
        if (this.operator.equalsIgnoreCase("or")) {
            pb = Specifications.or();
        } else {
            pb = Specifications.and();
        }
        if (filters != null) build(pb, filters);
        if (groups != null) groups.stream().forEach(group -> pb.predicate(group.build(distinct)));
        return pb.build(distinct);
    }

    public Group setOperator(String operator) {
        this.operator = operator;
        return this;
    }

    public Group addFilter(Filter filter) {
        this.filters.add(filter);
        return this;
    }

    public Group addFilters(List<Filter> filters) {
        this.filters.addAll(filters);
        return this;
    }


    public Group addGroup(Group group) {
        this.groups.add(group);
        return this;
    }

    private void build(PredicateBuilder pb, List<Filter> filters) {
        filters.stream().forEach(filter -> {
            switch (filter.operator.toUpperCase()) {
                case "EQ":
                    pb.eq(filter.field, getValue(filter));
                    break;
                case "NE":
                    pb.ne(filter.field, getValue(filter));
                    break;
                case "LIKE":
                    pb.like(filter.field, getLikeValues(filter));
                    break;
                case "NOT_LIKE":
                    pb.notLike(filter.field, getLikeValues(filter));
                    break;
                case "GT":
                    pb.gt(filter.field, (Comparable) getValue(filter));
                    break;
                case "LT":
                    pb.lt(filter.field, (Comparable) getValue(filter));
                    break;
                case "GTE":
                    pb.ge(filter.field, (Comparable) getValue(filter));
                    break;
                case "LTE":
                    pb.le(filter.field, (Comparable) getValue(filter));
                    break;
                case "IN":
                    Collection result = getValues(filter);
                    pb.in(filter.field, result);
                    break;
                case "NIN":
                    pb.notIn(filter.field, getValue(filter));
                    break;
            }
        });
    }

    private String[] getLikeValues(Filter filter) {
        String[] rows;
        Object o = getValue(filter);
        if (o instanceof Object[]) {
            rows = (String[]) o;
        } else {
            rows = new String[1];
            rows[0] = (String) o;
        }
        for (int i = 0; i < rows.length; i++) {
            String s = rows[i];
            if (s.indexOf("%") == -1) {
                rows[i] = "%" + s + "%";
            }
        }
        return rows;
    }

    private Collection getValues(Filter filter) {
        Collection result;
        Object value = getValue(filter);
        if (value instanceof Collection) {
            result = (Collection<?>) value;
        } else if (value instanceof Object[]) {
            result = Arrays.stream((Object[]) value).collect(Collectors.toList());
        } else {
            result = new ArrayList<>();
            result.add(value);
        }
        return result;
    }


    public Object getValue(Filter filter) {
        if (filter.getType() == null) return filter.value;
        if (ReflectUtil.isPrimitive(filter.value.getClass())) return Tools.convert(filter.getType(), filter.value);
        return filter.value;
    }
}
